package smtp;

import javax.mail.MessagingException;
import javax.mail.event.FolderEvent;
import javax.mail.event.FolderListener;
import javax.mail.event.StoreEvent;
import javax.mail.event.StoreListener;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;

import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPStore;

/**
 * This class handles the behavior of the user's Mailbox screen in response to user input
 * and subsequent IMAP requests.
 * 
 * A TreeSelectionLister interface is implemented because this class needs to know when changes have been made
 * to which nodes (folders) are viewable by the user.  Only information on viewed folders will be requested from the server.
 * Specifically, only information on a folder freshly set to visible will be updated (includes information of its viewed children).
 * 
 * 
 * The information subject to update is the following: Folder names, existing folders, total number of new messages
 * 
 * These folders are NOT opened (i.e. SELECTed in IMAP terminology) because only one folder may be selected per connection
 * 
 * @author chandlerr
 *
 */

/*
 * SUGGESTED CHANGES FOR LATER:
 * Needs a cleaner update.  Currently old info is deleted upon node collapse and new info added upon expand.
 * This is bad if the connection gets lost.  It would be nice to have kept a "cache".
 * 
 * Keep folder info "cached" until we are ready to switch all the old info out with the new info.
 * Also see how the IMAP classes deal with a bad connection.
 * 
 * 
 * I was going to have this class create a corresponding FolderController but it would need to handle a ListModel.
 * That seems like it should be done between the UI and FolderController class directly.
 * 
 * 
 * TO USE THIS CLASS:
 * Create an instance with an instantiated IMAPStore and the TreeModel from a JTree
 * Sign this class up for the JTree's TreeSelectionEvents and TreeExpansionEvents
 * 
 */
public class MailboxController implements TreeSelectionListener, TreeExpansionListener{

	IMAPStore store;
	FolderController currentFolder;
	TreeModel folderTree;
	IMAPFolder root;

	/*
	 * Data class for nodes.  Used to format display text.
	 * 
	 * Displays data on closed folders
	 */
	private class MailboxNode {
		IMAPFolder imapFolder;
		String name;
		int totalMsg;
		int newMsg;

		public MailboxNode (IMAPFolder f){
			this.imapFolder = f;
			this.name = f.getName();
			try {
				this.totalMsg = f.getMessageCount();
				this.newMsg = f.getNewMessageCount();
			} catch (MessagingException e) {
				System.out.println("MailboxController unable to get message counts from folder: " + this.name);
				e.printStackTrace();
			}
		}

		public String toString(){
			return (this.name + " (" + this.newMsg + "/" + this.totalMsg + ")");
		}
	}

	/**
	 * Constructor initializes the viewed folder tree with the first-level child directories of the root
	 * The current, or selected, folder is defaulted to be the inbox.
	 * 
	 * @param store
	 * @param mailboxScreen
	 */
	public MailboxController(IMAPStore store, JTree tree){
		this.store = store;

		try {
			root = (IMAPFolder) store.getDefaultFolder();
		} catch (MessagingException e) {
			System.out.println("MailboxController unable to get default folder.");
			e.printStackTrace();
		}
		DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(root);
		expandNode(rootNode); // inefficient - creates root object twice
		this.folderTree = new DefaultTreeModel(rootNode);
		
		tree.setModel(folderTree);
	}

	/**
	 * Adds first-level children to the indicated node
	 * 
	 * To investigate:
	 * What exactly does getNamespaces do?
	 * It's most efficient to request only first-level child folders with a LIST command with % char
	 * 
	 * Note: Folders are not opened by this function
	 * 
	 * @param node
	 */
	private void expandNode(DefaultMutableTreeNode node){
		IMAPFolder parent = ((MailboxNode) node.getUserObject()).imapFolder;

		// if the indicated folder has not been removed from the server,
		// then update it and display its children
		try {
			if (parent.exists()){
				node.setUserObject(new MailboxNode(parent));
				IMAPFolder[] children;

				children = (IMAPFolder[]) parent.list();

				// node.removeAllChildren(); should already be done upon collapse
				for(IMAPFolder child:children){
					node.add(new DefaultMutableTreeNode(new MailboxNode(child)));
				}

				((DefaultTreeModel) folderTree).reload(node);
			} else {
				node.removeFromParent();
				((DefaultTreeModel) folderTree).reload();
			}
		} catch (MessagingException e) {
			System.out.println("MailboxController Exception: " + e.getLocalizedMessage());
			e.printStackTrace();
		}

	}

	/**
	 * Use this method to get a folder to open with the FolderController
	 * 
	 * @param node
	 * @return
	 */
	public IMAPFolder getFolder(DefaultMutableTreeNode node){
		IMAPFolder returnFolder = null;

		// if the object is of the MailNode class and
		// if the IMAPFolder it contains belongs to this Controller's IMAPStore
		if(node.getUserObject().getClass().equals(MailboxNode.class)){
			IMAPFolder folder = ((MailboxNode)node.getUserObject()).imapFolder;
			if (folder.getStore().equals(this.store)){
				returnFolder = folder;
			}

		}
		return returnFolder;
	}

	/**
	 * Refresh all existing nodes on this tree.  Somewhat costly method, not needed, but good to have.
	 * 
	 * NOTE: This method does not check for new folders within the hierarchy.
	 * It only updates or deletes existing ones.  Bad.  Counterintuitive.  Either remove this function completely
	 * or make it also add new folders if they exist in a currently expanded folder.
	 * (Probably want to create a new tree model and compare it against the old one
	 * as you go to know what you need to do.)
	 */
	public void refreshAll(){
		refreshAll((DefaultMutableTreeNode)(folderTree.getRoot()));

		((DefaultTreeModel) folderTree).reload();
	}

	/**
	 * Private recursive method for refreshAll()
	 * 
	 * 1) Checks if the node's corresponding folder still exists on the server.
	 * 2) If it does, then it creates a new MailboxNode to display the most current info
	 * 		AND then it calls refreshAll on the node's children
	 * 3) If the folder didn't exist, then the node is deleted from the tree
	 * 
	 * NOTE: This method does not check for new folders within the hierarchy.
	 * It only updates or deletes existing ones.  Bad.  Either remove this function completely
	 * or make it also add new folders if they exist in a currently expanded folder.
	 * 
	 * @param node
	 */
	private void refreshAll(DefaultMutableTreeNode node){
		IMAPFolder imapFolder = ((MailboxNode)(node.getUserObject())).imapFolder;
		try {
			if(imapFolder.exists()){
				node.setUserObject(new MailboxNode(imapFolder));
				for(int i = 0; i < node.getChildCount(); i++){
					refreshAll((DefaultMutableTreeNode) node.getChildAt(i));
				}
			}else {
				node.removeFromParent();
			}
		} catch (MessagingException e) {
			System.out.println("Exception checking if IMAPFolder " + imapFolder + " exists.");
			e.printStackTrace();
		}
	}

	@Override
	public void valueChanged(TreeSelectionEvent arg0) {
		expandNode((DefaultMutableTreeNode)(arg0.getPath()).getLastPathComponent());

	}

	@Override
	public void treeCollapsed(TreeExpansionEvent arg0) {
		((DefaultMutableTreeNode)(arg0.getPath()).getLastPathComponent()).removeAllChildren();

	}

	@Override
	public void treeExpanded(TreeExpansionEvent arg0) {
		expandNode((DefaultMutableTreeNode)(arg0.getPath()).getLastPathComponent());

	}
}
